﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using SharedLib.XmlDataModel;
using SharedLib.Helpers;

namespace SharedLib.DataAccess
{
    abstract public class XmlDataProvider<T> : IDataModelWithIDProvider<T> where T : XmlDataModelWithAttributesAndID, new()
    {
        private object _lockhelper = new object();
        protected XmlDocument doc;
        protected XmlElement root;

        public event EventHandler<GenericEventArgs<IList<DataChangeSet<T>>>> ItemsChanged;
        public event EventHandler<GenericEventArgs<IList<T>>> ItemsAdded;
        public event EventHandler<GenericEventArgs<IList<T>>> ItemsRemoved;

        //public virtual string DataFolder
        //{
        //    get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Data"); }
        //}

        public virtual string FilePath
        {
            get { return AppDataHelper.GetAppDataFile("Data\\" + FileName); }
        }

        public virtual string GetMaxID()
        {
            return root.GetAttribute("max-id");
        }

        #region To be implemented
        public abstract string FileName { get; }
        public abstract string RootNodeName { get; }
        public abstract string DataItemPath { get; }
        #endregion

        #region Public Interface
        public virtual void LoadItems()
        {
            //if (!Directory.Exists(DataFolder))
            //{
            //    Directory.CreateDirectory(DataFolder);
            //}

            doc = new XmlDocument();
            if (!File.Exists(FilePath))
            {
                root = doc.AppendChild(doc.CreateElement(RootNodeName)) as XmlElement;
                root.SetAttribute("max-id", "0");
                doc.Save(FilePath);
            }
            else
            {
                doc.Load(FilePath);
                root = doc.SelectSingleNode(RootNodeName) as XmlElement;
                if (root == null) throw new Exception("Root Node \"" + RootNodeName + "\" not found!");
            }
        }

        public virtual void Save()
        {
            doc.Save(FilePath);
        }

        public virtual List<T> GetItems()
        {
            List<T> rtn = new List<T>();
            foreach (XmlNode node in doc.SelectNodes(DataItemPath))
            {
                T item = new T();
                item.LoadXmlNode(node);
                rtn.Add(item);
            }
            return rtn;
        }

        public virtual bool IsItemExisting(string id)
        {
            if (String.IsNullOrEmpty(id)) return false;
            return GetNodeByID(id) != null;
        }

        public virtual T GetItemByID(string id)
        {
            XmlNode node = GetNodeByID(id);
            if (node == null) return default(T);
            T rtn = new T();
            rtn.LoadXmlNode(node);
            return rtn;
        }

        public T GetItem(DataStructure.IQueryCondition<T> condition)
        {
            return condition.GetMatchedItem(GetItems());
        }

        public void AddItem(T rawItem)
        {
            IList<T> rawItems = new List<T>();
            rawItems.Add(rawItem);
            AddItems(rawItems);
        }

        public void AddItems(IList<T> rawItems)
        {
            lock (_lockhelper)
            {
                foreach (T rawItem in rawItems)
                {
                    DoAddItem(rawItem);
                }
                Save();
            } 
            ItemsAdded.InvokeEvent(this, new GenericEventArgs<IList<T>>(rawItems));
        }

        /// <summary>
        /// Core Add Item Logic
        /// </summary>
        /// <param name="rawItem"></param>
        protected virtual void DoAddItem(T rawItem)
        {
            int maxID = Convert.ToInt32(GetMaxID()) + 1;
            rawItem.ID = maxID.ToString();
            XmlElement node = rawItem.ToXmlNode(doc);
            ProcessItemToAdd(node, rawItem);
            root.AppendChild(node);
            root.SetAttribute("max-id", rawItem.ID);
        }

        public void UpdateItems(IList<DataChangeSet<T>> changes)
        {
            lock (_lockhelper)
            {
                foreach (DataChangeSet<T> change in changes)
                {
                    DoUpdateItem(change.OriginalData, change.Data);
                }
                Save();
            }
            ItemsChanged.InvokeEvent(this, new GenericEventArgs<IList<DataChangeSet<T>>>(changes));
        }

        public void UpdateItems(IList<T> newItems)
        {
            IList<DataChangeSet<T>> changes = new List<DataChangeSet<T>>();
            foreach (T newItem in newItems)
            {
                changes.Add(new DataChangeSet<T>(GetItemByID(newItem.ID), newItem));
            }
            UpdateItems(changes);
        }

        public void UpdateItem(T oldItem, T newItem)
        {
            IList<DataChangeSet<T>> list = new List<DataChangeSet<T>>();
            list.Add(new DataChangeSet<T>(oldItem, newItem));
            UpdateItems(list);
        }

        public void UpdateItem(T newItem)
        {
            UpdateItem(GetItemByID(newItem.ID), newItem);
        }

        /// <summary>
        /// Core Update Item logic
        /// </summary>
        /// <param name="oldItem"></param>
        /// <param name="newItem"></param>
        protected virtual void DoUpdateItem(T oldItem, T newItem)
        {
            XmlNode oldNode = GetNodeByID(oldItem.ID);
            if (oldNode != null)
            {
                //TODO:Exception Handling
                XmlHelper.UpdateXmlNode(newItem.ToXmlNode(doc), oldNode);
            }
        }

        public void RemoveItem(T item)
        {
            if (item == null) throw new ArgumentNullException("item");
            
            IList<T> items = new List<T>();
            items.Add(item);
            RemoveItems(items);

        }

        public void RemoveItems(IList<T> items)
        {
            lock (_lockhelper)
            {
                foreach (T item in items)
                {
                    DoRemoveItem(item);
                }
                Save();
            }
            ItemsRemoved.InvokeEvent(this, new GenericEventArgs<IList<T>>(items));
        }

        public void RemoveItem(string id)
        {
            RemoveItem(GetItemByID(id));
        }

        /// <summary>
        /// Core Remove Item Logic
        /// </summary>
        /// <param name="item"></param>
        protected virtual void DoRemoveItem(T item)
        {
            XmlNode oldNode = GetNodeByID(item.ID);
            if (oldNode != null)
            {
                //TODO:Exception Handling
                root.RemoveChild(oldNode);
            }
        }
        #endregion
        

        private XmlElement GetNodeByID(string id)
        {
            return doc.SelectSingleNode(String.Format(DataItemPath + "[@id='{0}']", id)) as XmlElement;
        }

        protected virtual void ProcessItemToAdd(XmlElement node, T rawItem)
        {
            
        }

        
    }
}
